在一些應用中我們會看到一些特殊的佈局方式比如 Pinterest 的瀑布流。
而我們只要通過自定義 UICollectionViewLayout 就可以實現。
可以設定畫面上最多會有幾張卡片,卡片向做滑動可以滑出畫面,向右可以將卡片滑入。
CardCell 只有放一張圖片,在 loadContent 後載入圖片、設定基本樣式。
class CardCell: UICollectionViewCell {
@IBOutlet weak var imageView: UIImageView!
var imageName:String?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func loadContent() {
if imageName == nil { return }
if let image = UIImage(named:imageName!) {
imageView.image = image
}
layer.cornerRadius = 20
layer.borderColor = UIColor.white.cgColor
layer.borderWidth = 2
}
}
通過自定義 UICollectionViewLayout 來定義出卡片滑動的佈局效果。
可以對 CardCollectionViewLayout 設定 itemSize / spacing / maximumVisibleItems
並且在設定的時候調用 invalidateLayout 方法,觸發畫面重新 render 機制,這裡會檢查 collectionView 是否存在。
public var itemSize: CGSize = CGSize(width: 250, height: 400) {
didSet{
if collectionView != nil {
invalidateLayout()
}
}
}
public var spacing: CGFloat = 16.0 {
didSet{
if collectionView != nil {
invalidateLayout()
}
}
}
public var maximumVisibleItems: Int = 4 {
didSet{
if collectionView != nil {
invalidateLayout()
}
}
}
attributes.center - 每張卡片在畫面上的位置,根據設定的 Spacing 讓卡片在 y 軸上有間距。
根據移動 UICollectionViewCell 的比例 (percentageDeltaOffset) 來改變即將登場卡片的 Alpha 值
// MARK: - compute layout
extension CardCollectionViewLayout {
func computeLayoutAttributesForItem(indexPath: IndexPath,
minVisibleIndex: Int,
contentCenterX: CGFloat,
deltaOffset: CGFloat,
percentageDeltaOffset: CGFloat) -> UICollectionViewLayoutAttributes {
if collectionView == nil { return UICollectionViewLayoutAttributes(forCellWith:indexPath)}
let attributes = UICollectionViewLayoutAttributes(forCellWith:indexPath)
let cardIndex = indexPath.row - minVisibleIndex
attributes.size = itemSize
attributes.center = CGPoint(x: contentCenterX + spacing * CGFloat(cardIndex),
y: collectionView!.bounds.midY + spacing * CGFloat(cardIndex))
attributes.zIndex = maximumVisibleItems - cardIndex
switch cardIndex {
case 0:
attributes.center.x -= deltaOffset
case 1..<maximumVisibleItems:
attributes.center.x -= spacing * percentageDeltaOffset
attributes.center.y -= spacing * percentageDeltaOffset
if cardIndex == maximumVisibleItems - 1 {
attributes.alpha = percentageDeltaOffset
}
default: break
}
return attributes
}
}
這次的佈局只有支持單一 section 所以在 prepare 的地方會先檢查 section 的數量。
// MARK: UICollectionViewLayout
extension CardCollectionViewLayout {
override open func prepare() {
super.prepare()
assert(collectionView?.numberOfSections == 1, "Multiple sections aren't supported!")
}
override open var collectionViewContentSize: CGSize {
if collectionView == nil { return CGSize.zero }
let itemsCount = CGFloat(collectionView!.numberOfItems(inSection: 0))
return CGSize(width: collectionView!.bounds.width * itemsCount,
height: collectionView!.bounds.height)
}
override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
if collectionView == nil { return nil }
let totalItemsCount = collectionView!.numberOfItems(inSection: 0)
let minVisibleIndex = max(0, Int(collectionView!.contentOffset.x) / Int(collectionView!.bounds.width))
let maxVisibleIndex = min(totalItemsCount, minVisibleIndex + maximumVisibleItems)
let contentCenterX = collectionView!.contentOffset.x + collectionView!.bounds.width / 2
let deltaOffset = Int(collectionView!.contentOffset.x) % Int(collectionView!.bounds.width)
let percentageDeltaOffset = CGFloat(deltaOffset) / collectionView!.bounds.width
var attributes = [UICollectionViewLayoutAttributes]()
for i in minVisibleIndex..<maxVisibleIndex {
let attribute = computeLayoutAttributesForItem(indexPath: IndexPath(item: i, section: 0),
minVisibleIndex: minVisibleIndex,
contentCenterX: contentCenterX,
deltaOffset: CGFloat(deltaOffset),
percentageDeltaOffset: percentageDeltaOffset)
attributes.append(attribute)
}
return attributes
}
override open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
layoutAttributesForElements 的計算是根據 in rect 範圍推算目前在畫面上的 index 然後計算對應的 attributes 後返回。
可以進一步的將計算過的 attribute 存起來,在這個方法中直接返回,這樣可以避免一直重複計算。
layoutAttributesForElements 以及 layout AttributesForItem 的關係?
在拖動 UICollectionViewCell 的過程中
collectionView.bounds 的大小是隨著往左滑動的卡片數量增長的嗎?
疑問 2
collectionView.bounds 的大小是隨著往左滑動的卡片數量增長的嗎?
==>collectionView.bounds不是固定的嗎?